1#![allow(dead_code)]
2mod block;
11mod dnd;
12mod event;
13mod tab;
14mod window;
15mod workspace;
16
17pub use block::*;
19pub use dnd::*;
20#[allow(unused_imports)]
21pub use event::*;
22pub use tab::*;
23pub use window::*;
24pub use workspace::*;
25
26use uuid::Uuid;
27
28use super::storage::store::Store;
29use super::storage::StoreError;
30use super::obj::*;
31
32pub const LAYOUT_ACTION_INSERT: &str = "insert";
35pub const LAYOUT_ACTION_INSERT_AT_INDEX: &str = "insertatindex";
36pub const LAYOUT_ACTION_REMOVE: &str = "remove";
37pub const LAYOUT_ACTION_CLEAR_TREE: &str = "cleartree";
38pub const LAYOUT_ACTION_REPLACE: &str = "replace";
39pub const LAYOUT_ACTION_SPLIT_HORIZONTAL: &str = "splithorizontal";
40pub const LAYOUT_ACTION_SPLIT_VERTICAL: &str = "splitvertical";
41
42pub fn ensure_initial_data(store: &Store) -> Result<bool, StoreError> {
48 let clients = store.get_all::<Client>()?;
49
50 if !clients.is_empty() {
51 let client = &clients[0];
53 if client.tempoid.is_empty() {
54 let mut client = client.clone();
55 client.tempoid = Uuid::new_v4().to_string();
56 store.update(&mut client)?;
57 }
58 for window_id in &client.windowids {
60 window::check_and_fix_window(store, window_id)?;
61 }
62 return Ok(false);
63 }
64
65 let first_launch = true;
67
68 let mut client = Client {
71 oid: Uuid::new_v4().to_string(),
72 windowids: vec![],
73 tempoid: String::new(),
74 meta: MetaMapType::new(),
75 ..Default::default()
76 };
77
78 store.insert(&mut client)?;
79
80 client.tempoid = Uuid::new_v4().to_string();
82 store.update(&mut client)?;
83
84 let ws = create_workspace(store, "Starter workspace")?;
86
87 let mut win = create_window(store, &ws.oid)?;
89
90 if let Ok(name) = std::env::var("AGENTMUX_WINDOW_NAME") {
97 let name = name.trim();
98 if !name.is_empty() {
99 win.meta.insert(
100 "window:displayname".to_string(),
101 serde_json::json!(name.chars().take(64).collect::<String>()),
102 );
103 store.update(&mut win)?;
104 }
105 }
106
107 client.windowids.push(win.oid.clone());
109 store.update(&mut client)?;
110
111 let tab = create_tab_with_opts(store, &ws.oid, "", true)?;
113
114 seed_default_layout(store, &tab.oid)?;
117
118 Ok(first_launch)
119}
120
121pub(crate) fn seed_default_layout(store: &Store, tab_id: &str) -> Result<(), StoreError> {
145 let tab = store.must_get::<Tab>(tab_id)?;
146
147 let mut agent_meta = MetaMapType::new();
148 agent_meta.insert("view".to_string(), serde_json::json!("agent"));
149 let agent_block = create_block(store, &tab.oid, agent_meta)?;
150
151 let mut sysinfo_meta = MetaMapType::new();
152 sysinfo_meta.insert("view".to_string(), serde_json::json!("sysinfo"));
153 let sysinfo_block = create_block(store, &tab.oid, sysinfo_meta)?;
154
155 let mut swarm_meta = MetaMapType::new();
156 swarm_meta.insert("view".to_string(), serde_json::json!("swarm"));
157 let swarm_block = create_block(store, &tab.oid, swarm_meta)?;
158
159 write_default_three_pane_layout(
160 store,
161 tab_id,
162 &agent_block.oid,
163 &sysinfo_block.oid,
164 &swarm_block.oid,
165 )
166}
167
168pub(crate) fn write_default_three_pane_layout(
183 store: &Store,
184 tab_id: &str,
185 agent_block_id: &str,
186 sysinfo_block_id: &str,
187 swarm_block_id: &str,
188) -> Result<(), StoreError> {
189 let tab = store.must_get::<Tab>(tab_id)?;
190
191 let agent_node_id = Uuid::new_v4().to_string();
194 let sysinfo_node_id = Uuid::new_v4().to_string();
195 let swarm_node_id = Uuid::new_v4().to_string();
196 let right_col_id = Uuid::new_v4().to_string();
197 let root_id = Uuid::new_v4().to_string();
198
199 let rootnode = LayoutNode {
201 id: root_id,
202 flex_direction: FlexDirection::Row,
203 size: 10.0,
204 data: None,
205 children: vec![
206 LayoutNode {
207 id: agent_node_id.clone(),
208 flex_direction: FlexDirection::Column,
209 size: 5.0,
210 children: Vec::new(),
211 data: Some(LayoutNodeData {
212 block_id: agent_block_id.to_string(),
213 ..Default::default()
214 }),
215 ..Default::default()
216 },
217 LayoutNode {
218 id: right_col_id,
219 flex_direction: FlexDirection::Column,
220 size: 5.0,
221 children: vec![
222 LayoutNode {
223 id: sysinfo_node_id.clone(),
224 flex_direction: FlexDirection::Row,
225 size: 2.0,
226 children: Vec::new(),
227 data: Some(LayoutNodeData {
228 block_id: sysinfo_block_id.to_string(),
229 ..Default::default()
230 }),
231 ..Default::default()
232 },
233 LayoutNode {
234 id: swarm_node_id.clone(),
235 flex_direction: FlexDirection::Row,
236 size: 8.0,
237 children: Vec::new(),
238 data: Some(LayoutNodeData {
239 block_id: swarm_block_id.to_string(),
240 ..Default::default()
241 }),
242 ..Default::default()
243 },
244 ],
245 data: None,
246 ..Default::default()
247 },
248 ],
249 ..Default::default()
250 };
251
252 let mut layout = store.must_get::<LayoutState>(&tab.layoutstate)?;
253 layout.rootnode = Some(rootnode);
254 layout.focusednodeid = agent_node_id.clone();
255 layout.leaforder = Some(vec![
256 LeafOrderEntry { nodeid: agent_node_id, blockid: agent_block_id.to_string() },
257 LeafOrderEntry { nodeid: sysinfo_node_id, blockid: sysinfo_block_id.to_string() },
258 LeafOrderEntry { nodeid: swarm_node_id, blockid: swarm_block_id.to_string() },
259 ]);
260 layout.pendingbackendactions = None;
261 store.update(&mut layout)?;
262 Ok(())
263}
264
265pub fn get_client(store: &Store) -> Result<Client, StoreError> {
267 let clients = store.get_all::<Client>()?;
268 clients.into_iter().next().ok_or(StoreError::NotFound)
269}
270
271#[cfg(test)]
276mod tests {
277 use super::*;
278
279 fn make_store() -> Store {
280 Store::open_in_memory().unwrap()
281 }
282
283 #[test]
284 fn test_ensure_initial_data_first_launch() {
285 let store = make_store();
286 let first = ensure_initial_data(&store).unwrap();
287 assert!(first);
288
289 let client = get_client(&store).unwrap();
291 assert_eq!(client.windowids.len(), 1);
292 assert!(!client.tempoid.is_empty());
293
294 let windows = store.get_all::<Window>().unwrap();
295 assert_eq!(windows.len(), 1);
296 assert_eq!(windows[0].pos.x, 0);
298 assert_eq!(windows[0].pos.y, 0);
299 assert_eq!(windows[0].winsize.width, 0);
300 assert_eq!(windows[0].winsize.height, 0);
301
302 let workspaces = store.get_all::<Workspace>().unwrap();
303 assert_eq!(workspaces.len(), 1);
304 assert_eq!(workspaces[0].name, "Starter workspace");
305 assert_eq!(workspaces[0].pinnedtabids.len(), 1);
307 assert_eq!(workspaces[0].tabids.len(), 0);
308
309 let tabs = store.get_all::<Tab>().unwrap();
310 assert_eq!(tabs.len(), 1);
311 assert_eq!(tabs[0].name, "tab1");
315 }
316
317 #[test]
318 fn test_ensure_initial_data_idempotent() {
319 let store = make_store();
320 let first = ensure_initial_data(&store).unwrap();
321 assert!(first);
322
323 let second = ensure_initial_data(&store).unwrap();
324 assert!(!second);
325
326 assert_eq!(store.count::<Client>().unwrap(), 1);
328 }
329
330 #[test]
331 fn test_create_and_delete_workspace() {
332 let store = make_store();
333 let ws = create_workspace(&store, "Test WS").unwrap();
334 assert_eq!(ws.name, "Test WS");
335
336 let t1 = create_tab(&store, &ws.oid).unwrap();
338 let t2 = create_tab(&store, &ws.oid).unwrap();
339
340 let ws = get_workspace(&store, &ws.oid).unwrap();
341 assert_eq!(ws.tabids.len(), 2);
342
343 let t1_oid = t1.oid.clone();
345 let t2_oid = t2.oid.clone();
346 delete_workspace(&store, &ws.oid).unwrap();
347 assert!(store.get::<Workspace>(&ws.oid).unwrap().is_none());
348 assert!(store.get::<Tab>(&t1_oid).unwrap().is_none());
349 assert!(store.get::<Tab>(&t2_oid).unwrap().is_none());
350 }
351
352 #[test]
353 fn test_create_and_delete_tab() {
354 let store = make_store();
355 let ws = create_workspace(&store, "WS").unwrap();
356 let tab1 = create_tab(&store, &ws.oid).unwrap();
357 let tab2 = create_tab(&store, &ws.oid).unwrap();
358
359 let ws = get_workspace(&store, &ws.oid).unwrap();
360 assert_eq!(ws.tabids.len(), 2);
361 assert_eq!(ws.activetabid, tab1.oid);
362
363 delete_tab(&store, &ws.oid, &tab1.oid).unwrap();
365 let ws = get_workspace(&store, &ws.oid).unwrap();
366 assert_eq!(ws.tabids.len(), 1);
367 assert_eq!(ws.activetabid, tab2.oid);
368 }
369
370 #[test]
371 fn test_set_active_tab() {
372 let store = make_store();
373 let ws = create_workspace(&store, "WS").unwrap();
374 let _tab1 = create_tab(&store, &ws.oid).unwrap();
375 let tab2 = create_tab(&store, &ws.oid).unwrap();
376
377 set_active_tab(&store, &ws.oid, &tab2.oid).unwrap();
378 let ws = get_workspace(&store, &ws.oid).unwrap();
379 assert_eq!(ws.activetabid, tab2.oid);
380
381 let result = set_active_tab(&store, &ws.oid, "nonexistent");
383 assert!(result.is_err());
384 }
385
386 #[test]
387 fn test_create_and_delete_block() {
388 let store = make_store();
389 let ws = create_workspace(&store, "WS").unwrap();
390 let tab = create_tab(&store, &ws.oid).unwrap();
391
392 let mut meta = MetaMapType::new();
393 meta.insert("view".to_string(), serde_json::json!("term"));
394 let block = create_block(&store, &tab.oid, meta).unwrap();
395
396 let tab = store.must_get::<Tab>(&tab.oid).unwrap();
397 assert_eq!(tab.blockids.len(), 1);
398 assert_eq!(tab.blockids[0], block.oid);
399
400 let loaded = store.must_get::<Block>(&block.oid).unwrap();
401 assert_eq!(loaded.parentoref, format!("tab:{}", tab.oid));
402 assert_eq!(loaded.meta.get("view").unwrap(), "term");
403
404 delete_block(&store, &tab.oid, &block.oid).unwrap();
405 assert!(store.get::<Block>(&block.oid).unwrap().is_none());
406 let tab = store.must_get::<Tab>(&tab.oid).unwrap();
407 assert!(tab.blockids.is_empty());
408 }
409
410 #[test]
411 fn seed_default_layout_creates_three_pane_layout() {
412 let store = make_store();
417 let ws = create_workspace(&store, "WS").unwrap();
418 let tab = create_tab(&store, &ws.oid).unwrap();
419 assert!(tab.blockids.is_empty(), "fresh tab starts blank");
420 let layout = store.must_get::<LayoutState>(&tab.layoutstate).unwrap();
421 assert!(layout.rootnode.is_none(), "fresh tab has no layout");
422
423 seed_default_layout(&store, &tab.oid).unwrap();
424
425 let tab = store.must_get::<Tab>(&tab.oid).unwrap();
427 assert_eq!(tab.blockids.len(), 3, "agent + sysinfo + swarm");
428 let views: Vec<String> = tab
429 .blockids
430 .iter()
431 .map(|bid| {
432 store
433 .must_get::<Block>(bid)
434 .unwrap()
435 .meta
436 .get("view")
437 .and_then(|v| v.as_str())
438 .unwrap_or_default()
439 .to_string()
440 })
441 .collect();
442 assert!(views.contains(&"agent".to_string()));
443 assert!(views.contains(&"sysinfo".to_string()));
444 assert!(views.contains(&"swarm".to_string()));
445
446 let layout = store.must_get::<LayoutState>(&tab.layoutstate).unwrap();
448 assert!(layout.rootnode.is_some(), "rootnode must be populated, not blank");
449 assert_eq!(layout.leaforder.as_ref().unwrap().len(), 3);
450 }
451
452 #[test]
453 fn test_create_and_close_window() {
454 let store = make_store();
455 ensure_initial_data(&store).unwrap();
456
457 let client = get_client(&store).unwrap();
458 let initial_count = client.windowids.len();
459
460 let ws = create_workspace(&store, "WS2").unwrap();
461 let window = create_window(&store, &ws.oid).unwrap();
462
463 let mut client = get_client(&store).unwrap();
465 client.windowids.push(window.oid.clone());
466 store.update(&mut client).unwrap();
467
468 close_window(&store, &window.oid).unwrap();
469 let client = get_client(&store).unwrap();
470 assert_eq!(client.windowids.len(), initial_count);
471 assert!(store.get::<Window>(&window.oid).unwrap().is_none());
472 }
473
474 #[test]
475 fn test_focus_window() {
476 let store = make_store();
477 ensure_initial_data(&store).unwrap();
478
479 let ws = create_workspace(&store, "WS2").unwrap();
480 let w2 = create_window(&store, &ws.oid).unwrap();
481 let mut client = get_client(&store).unwrap();
482 client.windowids.push(w2.oid.clone());
483 store.update(&mut client).unwrap();
484
485 focus_window(&store, &w2.oid).unwrap();
487 let client = get_client(&store).unwrap();
488 assert_eq!(client.windowids[0], w2.oid);
489 }
490
491 #[test]
492 fn test_switch_workspace() {
493 let store = make_store();
494 ensure_initial_data(&store).unwrap();
495
496 let client = get_client(&store).unwrap();
497 let window_id = &client.windowids[0];
498 let window = store.must_get::<Window>(window_id).unwrap();
499 let old_ws = window.workspaceid.clone();
500
501 let new_ws = create_workspace(&store, "New WS").unwrap();
502 switch_workspace(&store, window_id, &new_ws.oid).unwrap();
503
504 let window = store.must_get::<Window>(window_id).unwrap();
505 assert_eq!(window.workspaceid, new_ws.oid);
506 assert_ne!(window.workspaceid, old_ws);
507 }
508
509 #[test]
510 fn test_resolve_block_id_prefix() {
511 let store = make_store();
512 let ws = create_workspace(&store, "WS").unwrap();
513 let tab = create_tab(&store, &ws.oid).unwrap();
514
515 let meta = MetaMapType::new();
516 let block = create_block(&store, &tab.oid, meta).unwrap();
517 let prefix = &block.oid[..8];
518
519 let resolved = resolve_block_id_from_prefix(&store, &tab.oid, prefix).unwrap();
520 assert_eq!(resolved, block.oid);
521
522 let result = resolve_block_id_from_prefix(&store, &tab.oid, "00000000");
524 assert!(result.is_err());
525 }
526
527 #[test]
528 fn test_list_workspaces() {
529 let store = make_store();
530 create_workspace(&store, "WS1").unwrap();
531 create_workspace(&store, "WS2").unwrap();
532 create_workspace(&store, "WS3").unwrap();
533
534 let all = list_workspaces(&store).unwrap();
535 assert_eq!(all.len(), 3);
536 }
537
538 #[test]
539 fn test_check_and_fix_window_missing_workspace() {
540 let store = make_store();
541 let mut window = Window {
543 oid: Uuid::new_v4().to_string(),
544 workspaceid: "nonexistent".to_string(),
545 pos: Point { x: 0, y: 0 },
546 winsize: WinSize {
547 width: 800,
548 height: 600,
549 },
550 meta: MetaMapType::new(),
551 ..Default::default()
552 };
553 store.insert(&mut window).unwrap();
554
555 window::check_and_fix_window(&store, &window.oid).unwrap();
556
557 let fixed = store.must_get::<Window>(&window.oid).unwrap();
559 assert_ne!(fixed.workspaceid, "nonexistent");
560 let ws = store.must_get::<Workspace>(&fixed.workspaceid).unwrap();
561 assert_eq!(ws.tabids.len(), 1); }
563
564 #[test]
565 fn test_move_block_to_tab() {
566 let store = make_store();
567 let ws = create_workspace(&store, "WS").unwrap();
568 let tab1 = create_tab(&store, &ws.oid).unwrap();
569 let tab2 = create_tab(&store, &ws.oid).unwrap();
570
571 let meta = MetaMapType::new();
572 let block = create_block(&store, &tab1.oid, meta).unwrap();
573
574 let t1 = store.must_get::<Tab>(&tab1.oid).unwrap();
576 assert_eq!(t1.blockids.len(), 1);
577 assert_eq!(t1.blockids[0], block.oid);
578
579 move_block_to_tab(&store, &block.oid, &tab1.oid, &tab2.oid, &ws.oid, false).unwrap();
581
582 let t1 = store.must_get::<Tab>(&tab1.oid).unwrap();
584 let t2 = store.must_get::<Tab>(&tab2.oid).unwrap();
585 assert!(t1.blockids.is_empty());
586 assert_eq!(t2.blockids.len(), 1);
587 assert_eq!(t2.blockids[0], block.oid);
588
589 let b = store.must_get::<Block>(&block.oid).unwrap();
591 assert_eq!(b.parentoref, format!("tab:{}", tab2.oid));
592 }
593
594 #[test]
595 fn test_move_block_to_tab_auto_close() {
596 let store = make_store();
597 let ws = create_workspace(&store, "WS").unwrap();
598 let tab1 = create_tab(&store, &ws.oid).unwrap();
599 let tab2 = create_tab(&store, &ws.oid).unwrap();
600
601 let block = create_block(&store, &tab1.oid, MetaMapType::new()).unwrap();
602
603 move_block_to_tab(&store, &block.oid, &tab1.oid, &tab2.oid, &ws.oid, true).unwrap();
605
606 assert!(store.get::<Tab>(&tab1.oid).unwrap().is_none());
608
609 let ws = store.must_get::<Workspace>(&ws.oid).unwrap();
611 assert_eq!(ws.tabids.len(), 1);
612 assert_eq!(ws.tabids[0], tab2.oid);
613 }
614
615 #[test]
616 fn test_move_block_same_tab_noop() {
617 let store = make_store();
618 let ws = create_workspace(&store, "WS").unwrap();
619 let tab = create_tab(&store, &ws.oid).unwrap();
620 let block = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
621
622 move_block_to_tab(&store, &block.oid, &tab.oid, &tab.oid, &ws.oid, false).unwrap();
624
625 let t = store.must_get::<Tab>(&tab.oid).unwrap();
626 assert_eq!(t.blockids.len(), 1);
627 }
628
629 #[test]
630 fn test_promote_block_to_tab() {
631 let store = make_store();
632 let ws = create_workspace(&store, "WS").unwrap();
633 let tab = create_tab(&store, &ws.oid).unwrap();
634 let block = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
635
636 let new_tab = promote_block_to_tab(&store, &block.oid, &tab.oid, &ws.oid, false).unwrap();
638
639 let old_tab = store.must_get::<Tab>(&tab.oid).unwrap();
641 assert!(old_tab.blockids.is_empty());
642
643 let nt = store.must_get::<Tab>(&new_tab.oid).unwrap();
645 assert_eq!(nt.blockids.len(), 1);
646 assert_eq!(nt.blockids[0], block.oid);
647
648 let ws = store.must_get::<Workspace>(&ws.oid).unwrap();
650 assert_eq!(ws.tabids.len(), 2);
651
652 assert_eq!(ws.activetabid, new_tab.oid);
654 }
655
656 #[test]
657 fn test_reorder_tab() {
658 let store = make_store();
659 let ws = create_workspace(&store, "WS").unwrap();
660 let tab1 = create_tab(&store, &ws.oid).unwrap();
661 let tab2 = create_tab(&store, &ws.oid).unwrap();
662 let tab3 = create_tab(&store, &ws.oid).unwrap();
663
664 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
666 assert_eq!(ws_data.tabids, vec![tab1.oid.clone(), tab2.oid.clone(), tab3.oid.clone()]);
667
668 reorder_tab(&store, &ws.oid, &tab3.oid, 0).unwrap();
670
671 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
672 assert_eq!(ws_data.tabids, vec![tab3.oid.clone(), tab1.oid.clone(), tab2.oid.clone()]);
673
674 reorder_tab(&store, &ws.oid, &tab1.oid, 99).unwrap();
676
677 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
678 assert_eq!(ws_data.tabids, vec![tab3.oid.clone(), tab2.oid.clone(), tab1.oid.clone()]);
679 }
680
681 #[test]
682 fn test_move_tab_to_workspace() {
683 let store = make_store();
684 let ws1 = create_workspace(&store, "WS1").unwrap();
685 let ws2 = create_workspace(&store, "WS2").unwrap();
686 let tab1 = create_tab(&store, &ws1.oid).unwrap();
687 let tab2 = create_tab(&store, &ws1.oid).unwrap();
688 let tab3 = create_tab(&store, &ws2.oid).unwrap();
689
690 set_active_tab(&store, &ws1.oid, &tab1.oid).unwrap();
692
693 move_tab_to_workspace(&store, &tab2.oid, &ws1.oid, &ws2.oid, None).unwrap();
695
696 let ws1_data = store.must_get::<Workspace>(&ws1.oid).unwrap();
697 let ws2_data = store.must_get::<Workspace>(&ws2.oid).unwrap();
698
699 assert_eq!(ws1_data.tabids, vec![tab1.oid.clone()]);
700 assert!(ws2_data.tabids.contains(&tab2.oid));
701 assert!(ws2_data.tabids.contains(&tab3.oid));
702 assert_eq!(ws2_data.activetabid, tab2.oid);
704 }
705
706 #[test]
707 fn test_move_tab_to_workspace_last_tab_blocked() {
708 let store = make_store();
709 let ws1 = create_workspace(&store, "WS1").unwrap();
710 let ws2 = create_workspace(&store, "WS2").unwrap();
711 let tab1 = create_tab(&store, &ws1.oid).unwrap();
712 let _tab2 = create_tab(&store, &ws2.oid).unwrap();
713
714 let result = move_tab_to_workspace(&store, &tab1.oid, &ws1.oid, &ws2.oid, None);
716 assert!(result.is_err());
717 }
718
719 #[test]
720 fn test_tear_off_block() {
721 let store = make_store();
722 let ws = create_workspace(&store, "WS").unwrap();
723 let tab = create_tab(&store, &ws.oid).unwrap();
724 let block = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
725 let block2 = create_block(&store, &tab.oid, MetaMapType::new()).unwrap();
726
727 let new_ws = tear_off_block(&store, &block.oid, &tab.oid, &ws.oid, true).unwrap();
729
730 let tab_data = store.must_get::<Tab>(&tab.oid).unwrap();
732 assert!(!tab_data.blockids.contains(&block.oid));
733 assert!(tab_data.blockids.contains(&block2.oid));
734
735 assert!(!new_ws.oid.is_empty());
737 assert_eq!(new_ws.tabids.len(), 1);
738 let new_tab = store.must_get::<Tab>(&new_ws.tabids[0]).unwrap();
739 assert!(new_tab.blockids.contains(&block.oid));
740
741 let block_data = store.must_get::<Block>(&block.oid).unwrap();
743 assert_eq!(block_data.parentoref, format!("tab:{}", new_tab.oid));
744 }
745
746 #[test]
747 fn test_tear_off_tab() {
748 let store = make_store();
749 let ws = create_workspace(&store, "WS").unwrap();
750 let tab1 = create_tab(&store, &ws.oid).unwrap();
751 let tab2 = create_tab(&store, &ws.oid).unwrap();
752 set_active_tab(&store, &ws.oid, &tab1.oid).unwrap();
753
754 let new_ws = tear_off_tab(&store, &tab2.oid, &ws.oid).unwrap();
756
757 let ws_data = store.must_get::<Workspace>(&ws.oid).unwrap();
759 assert_eq!(ws_data.tabids, vec![tab1.oid.clone()]);
760
761 assert_eq!(new_ws.tabids, vec![tab2.oid.clone()]);
763 assert_eq!(new_ws.activetabid, tab2.oid);
764 }
765
766 #[test]
767 fn test_tear_off_last_tab_blocked() {
768 let store = make_store();
769 let ws = create_workspace(&store, "WS").unwrap();
770 let tab1 = create_tab(&store, &ws.oid).unwrap();
771
772 let result = tear_off_tab(&store, &tab1.oid, &ws.oid);
774 assert!(result.is_err());
775 }
776}